/**
 * @author erond
 */

package master;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Timestamp;
import java.util.Observable;
import javax.swing.event.EventListenerList;
import commonUtility.FtpClientThread;

/**
 * BeforeGatewayLinkThread è la classe che genera thread in grado di implementare la parte di comunicazione
 * GATEWAY-GATEWAY (con il nodo PRECEDENTE che avrà avviato un AfterGatewayLinkThread)
 */
public class BeforeGatewayLink extends Observable implements Runnable {

	private ServerSocket beforeGatewayLink_ServerInputSocket = null;
	private BeforeGatewayLink_ServerInput beforeGatewayLink_ServerInput = null;
	private BeforeGatewayLink_ClientOutput beforeGatewayLink_ClientOutput = null;
	private Thread beforeGatewayLink_ServerInputThread = null;
	/**
	 * @return the beforeGatewayLink_ServerInputThread
	 */
	public Thread getBeforeGatewayLink_ServerInputThread() {
		return beforeGatewayLink_ServerInputThread;
	}

	/**
	 * @return the beforeGatewayLink_ClientOutputThread
	 */
	public Thread getBeforeGatewayLink_ClientOutputThread() {
		return beforeGatewayLink_ClientOutputThread;
	}

	private Thread beforeGatewayLink_ClientOutputThread = null;
	private MasterJobServer masterJobServer = null;
	private int beforeNeighbour_serverInputPort = 0;
	private InetAddress beforeNeighbour_serverIP = null;
	private InetAddress slaveIP = null;
	private int slaveDeleteServerPort_before = 0;
	private MasterMonitor masterMonitor = null;
	private String consoleMessage="";

	/**
	 * @return the consoleMessage
	 */
	public String getConsoleMessage() {
		return consoleMessage;
	}

	/**
	 * @return the beforeGatewayLink_ServerInputSocket
	 */
	public ServerSocket getBeforeGatewayLink_ServerInputSocket() {
		return beforeGatewayLink_ServerInputSocket;
	}

	/**
	 * @return the beforeGatewayLink_ServerInput
	 */
	public BeforeGatewayLink_ServerInput getBeforeGatewayLink_ServerInput() {
		return beforeGatewayLink_ServerInput;
	}

	/**
	 * @return the beforeGatewayLink_ClientOutput
	 */
	public BeforeGatewayLink_ClientOutput getBeforeGatewayLink_ClientOutput() {
		return beforeGatewayLink_ClientOutput;
	}

	/**
	 * @return the server_serverInputPort
	 */
	public int getServer_serverInputPort() {
		return beforeNeighbour_serverInputPort;
	}

	/**
	 * @return the server_serverIP
	 */
	public InetAddress getServer_serverIP() {
		return beforeNeighbour_serverIP;
	}

	/**
	 * @return the slaveIP
	 */
	public InetAddress getSlaveIP() {
		return slaveIP;
	}

	/**
	 * @return the slaveDeleteServerPort_before
	 */
	public int getSlaveDeleteServerPort_before() {
		return slaveDeleteServerPort_before;
	}

	public void setMasterJobServer (MasterJobServer m) {
		masterJobServer = m;
	}

	public MasterJobServer getMasterJobServer() {
		return masterJobServer;
	}

	private Object semaphore;

	/**
	 * @return the semaphore
	 */
	public Object getSemaphore() {
		return semaphore;
	}

	/**
	 * @param semaphore the semaphore to set
	 */
	public void setSemaphore(Object semaphore) {
		this.semaphore = semaphore;
	}

	/**
	 * Thread che si occupa della comunicazione in input. Creata la connessione da BeforeGatewayLinkThread
	 * questo thread rimane in attesa che eventualmente un gateway vicino (PRECEDENTE) gli invii
	 * ad un certo punto un file before_thief.txt contenente i macaddress per i quali vuole "usurpare" il
	 * servizio (load balancing).
	 * @author erond
	 *
	 */
	class BeforeGatewayLink_ServerInput extends Observable implements Runnable {

		private Socket localBeforeGatewayLink_ClientSocket = null;
		private ServerSocket localGatewayServerSocket = null;
		public MasterMonitor masterMonitor = null;
		private String lastSignalSent="";
		private String lastSignalReceived="";
		private MasterDeleteClient_BeforeLinkThread masterDeleteClient_BeforeLinkThread = null;
		/**
		 * @return the masterDeleteClient_BeforeLinkThread
		 */
		public MasterDeleteClient_BeforeLinkThread getMasterDeleteClient_BeforeLinkThread() {
			return masterDeleteClient_BeforeLinkThread;
		}

		private String consoleMessage="";


		/**
		 * @return the consoleMessage
		 */
		public String getConsoleMessage() {
			return consoleMessage;
		}

		/**
		 * @return the lastSignalSent
		 */
		public String getLastSignalSent() {
			return lastSignalSent;
		}

		/**
		 * @return the lastSignalReceived
		 */
		public String getLastSignalReceived() {
			return lastSignalReceived;
		}

		/**
		 * @return the localBeforeGatewayLink_ClientSocket
		 */
		public Socket getLocalBeforeGatewayLink_ClientSocket() {
			return localBeforeGatewayLink_ClientSocket;
		}

		/**
		 * @return the localGatewayServerSocket
		 */
		public ServerSocket getLocalGatewayServerSocket() {
			return localGatewayServerSocket;
		}

		private DataInputStream inSock = null;
		private DataOutputStream outSock = null;

		public BeforeGatewayLink_ServerInput(ServerSocket localGatewayServerSocket,MasterMonitor monitor) {
			this.localGatewayServerSocket = localGatewayServerSocket;
			this.masterMonitor=monitor;
			addObserver(masterMonitor);
		}

		public void run() {

			consoleMessage="BeforeGatewayLink_ServerInputThread avviato...";
			setChanged();
			notifyObservers(this);
			System.out.println(consoleMessage);

			try {
				while(true) {

					masterDeleteClient_BeforeLinkThread = 
						new MasterDeleteClient_BeforeLinkThread(slaveIP, slaveDeleteServerPort_before);
					masterDeleteClient_BeforeLinkThread.setName("masterDeleteClient_BeforeLinkThread");

					try {
						/**
						 * Il Thread,una volta creata la socket si pone in attesa che la connessione
						 * venga accettata anche lato client. Attende inoltre che venga effettuata una richiesta
						 * di servizio. BLOCCANTE!!!
						 */
						localBeforeGatewayLink_ClientSocket=localGatewayServerSocket.accept();
						consoleMessage="BeforeGatewayLink_ServerInputThread: sbloccato dalla accept";
						setChanged();
						notifyObservers(this);
						System.out.println(consoleMessage);
						if (localBeforeGatewayLink_ClientSocket.isConnected()) System.out.println("BeforeGatewayLink_ServerInput LOCALSOCKET CONNECTED!");
					}
					catch (IOException ioe) {
						System.out
						.println("BeforeGatewayLink_ServerInputThread: Problemi nella masterGatewayServerSocket.accept() "
								+ "su socket: "+localGatewayServerSocket);
						ioe.printStackTrace();
						return;
					}
					catch (Exception e) {
						//generica eccezione
						System.out.println("BeforeGatewayLink_ServerInputThread: ECCEZIONE "+"su socket: "+localBeforeGatewayLink_ClientSocket);
						e.printStackTrace();
					}


					try {
						// creazione stream di input e out da socket
						inSock = new DataInputStream(localBeforeGatewayLink_ClientSocket.getInputStream());
						outSock = new DataOutputStream(localBeforeGatewayLink_ClientSocket.getOutputStream());
					}
					catch (IOException ioe) {
						System.out
						.println("BeforeGatewayLink_ServerInputThread: Problemi nella creazione degli stream di input/output "
								+ "su socket: "+localBeforeGatewayLink_ClientSocket);
						ioe.printStackTrace();
						return;
					}


					/**
					 * 1) Mi metto in attesa e ricevo DAL GATEWAY BEFORE il file after_thief.txt (che io salvo come
					 * before_wants_thief.txt) contenente i macaddress per i quali vuole assumere il controllo di servizio
					 */


					consoleMessage=Thread.currentThread().getName()+": Attendo di ricevere AFTERTHIEFREADY dal GatewayServer_ClientOutput_before...";
					setChanged();
					notifyObservers(this);
					System.out.println(consoleMessage);

					String signal=null;

					try {
						while(inSock.available()>=0) {
							signal=inSock.readUTF();
							if(signal.equalsIgnoreCase("AFTERTHIEFREADY")) {
								lastSignalReceived=signal;
								consoleMessage=Thread.currentThread().getName()+": Ricevuto "+signal+" ..avvio la procedura!";
								setChanged();
								notifyObservers(this);
								System.out.println(consoleMessage);
								/**
								 * se ricevo questa stringa vuol dire che il vicino before ha inviato al mio ftp server
								 * il file contenente i mac address relativi ai job che vuole usurparmi
								 * CONTROLLO DI SICUREZZA: invio al vicino la stringa AFTERTHIEFREADYRECEIVED
								 */
								outSock.writeUTF("AFTERTHIEFREADYRECEIVED");
								lastSignalSent="AFTERTHIEFREADYRECEIVED";
								setChanged();
								notifyObservers(this);
								break;
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
						System.exit(1);
					}


					try {
						outSock.flush();
					} catch (IOException ioe) {
						System.err.println(Thread.currentThread().getName()+": Eccezione nella flush");
						ioe.printStackTrace();
					}

					Timestamp timestamp = new Timestamp(System.currentTimeMillis());
					consoleMessage="["+timestamp+"] BeforeGatewayLink_ServerInputThread: ricevuto before_wants_thief.txt dal vicino BEFORE";
					setChanged();
					notifyObservers(this);
					System.out.println(consoleMessage);

					/**
					 * 2) A questo punto devo inviare il file before_wants_thief.txt allo slave.
					 *    Delego il tutto al MasterDeleteClient_BeforeLinkThread e mi metto in attesa che
					 *    lui termini. A quel punto sarà disponibile il file send_before_transfer_job.xml... 
					 */

					masterDeleteClient_BeforeLinkThread.start();

					setChanged();
					notifyObservers(this);
					try {
						//attendo finchè il thread non completa il suo run()
						masterDeleteClient_BeforeLinkThread.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					setChanged();
					notifyObservers(this);

					/**
					 * 3) Non mi manca che far "rimbalzare" al BEFORE GATEWAY il file send_before_transfer_job.xml
					 *    ricevuto dallo SLAVE contenente i JOB che voleva usurparmi. FINE.
					 */

					FtpClientThread send_before_transfer_job = new FtpClientThread(beforeNeighbour_serverIP, 2121, "./gatewayTmp/send_before_transfer_job.xml", "got_before_transfer_job.xml");
					send_before_transfer_job.setName("BeforeGatewayLink_ServerInput_ftpClientThread_send_before_transfer_job");
					send_before_transfer_job.start();

					//Attendo che il trasferimento sia completato
					try {
						send_before_transfer_job.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					while(true) {
						try {
							outSock.writeUTF("BEFORETRANSFERJOBREADY");
							lastSignalSent="BEFORETRANSFERJOBREADY";
							setChanged();
							notifyObservers(this);
							while(inSock.available()>=0) {
								signal=inSock.readUTF();
								if(signal.equalsIgnoreCase("BEFORETRANSFERJOBREADYRECEIVED")) {
									lastSignalReceived=signal;
									consoleMessage=Thread.currentThread().getName()+": Ricevuto "+signal+" ..continuo!";
									setChanged();
									notifyObservers(this);
									System.out.println(consoleMessage);
									/**
									 * se ricevo questa stringa vuol dire che lo slave ha ricevuto BEFORETRANSFERJOBREADY
									 */
									break;
								}
							}
							break;
						} catch (IOException e) {
							e.printStackTrace();
						}
					}


					try {
						outSock.flush();
					} catch (IOException ioe) {
						System.err.println(Thread.currentThread().getName()+": Eccezione nella flush");
						ioe.printStackTrace();
					}

					timestamp = new Timestamp(System.currentTimeMillis());
					consoleMessage="["+timestamp+"] BeforeGatewayLink_ServerInputThread: inviato send_before_transfer_job.xml al vicino BEFORE!";
					setChanged();
					notifyObservers(this);
					System.out.println(consoleMessage);


					consoleMessage="BeforeGatewayLink_ServerInputThread: finito il servizio. Torno in attesa di richieste...";
					setChanged();
					notifyObservers(this);
					System.out.println(consoleMessage);

				}//fine while(true)

			} catch(Exception e) {
				System.err.println("BeforeGatewayLink_ServerInputThread: Errore,chiudo...\n");
				e.printStackTrace();
				try {
					localBeforeGatewayLink_ClientSocket.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}

		}//fine run()

	}//fine BeforeGatewayLink_ServerInputThread


	/**
	 * Thread che una volta creato va subito in wait(),e viene sbloccato solo nel momento in
	 * cui diventa necessario contattare il vicino (PRECEDENTE) per inviargli after_thief.txt, file
	 * contenente i macaddress dei client da "rubare".
	 * @author erond
	 *
	 */
	public class BeforeGatewayLink_ClientOutput extends Observable implements Runnable {

		private Socket localBeforeGatewayLink_ClientSocket = null;
		/**
		 * @return the localBeforeGatewayLink_ClientSocket
		 */
		public Socket getLocalBeforeGatewayLink_ClientSocket() {
			return localBeforeGatewayLink_ClientSocket;
		}

		private DataInputStream inSock = null;
		private DataOutputStream outSock = null;
		public EventListenerList listenerList = null;
		private MasterMonitor masterMonitor = null;
		private MasterJobServer masterToNotify = null;
		private String lastSignalSent="";
		/**
		 * @return the lastSignalSent
		 */
		public String getLastSignalSent() {
			return lastSignalSent;
		}

		private String consoleMessage="";

		/**
		 * @return the consoleMessage
		 */
		public String getConsoleMessage() {
			return consoleMessage;
		}

		/**
		 * @return the lastSignalReceived
		 */
		public String getLastSignalReceived() {
			return lastSignalReceived;
		}

		private String lastSignalReceived="";

		public BeforeGatewayLink_ClientOutput(MasterMonitor monitor) {
			this.masterMonitor=monitor;
			this.addObserver(masterMonitor);
		}

		public void run() {

			while(true){

				consoleMessage="BeforeGatewayLink_ClientOutputThread: Inizio ciclo...";
				setChanged();
				notifyObservers(this);
				System.out.println(consoleMessage);

				synchronized (this) {

					/**
					 * mi pongo subito in attesa di essere risvegliato. Al risveglio deve essere pronto
					 * all'invio al gateway vicino (PRECEDENTE, quindi lo riceverà il thread
					 * GatewayServer_ServerInputThread) del file before_thief.txt.
					 */
					try {
						this.wait();
						consoleMessage="BeforeGatewayLink_ClientOutputThread: SBLOCCATO dal masterJobServerThread!";
						setChanged();
						notifyObservers(this);
						System.out.println(consoleMessage);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				/**
				 * questo costruttore dovrà avere i parametri del gateway after
				 */
				try {
					localBeforeGatewayLink_ClientSocket = new Socket(beforeNeighbour_serverIP,beforeNeighbour_serverInputPort);
					consoleMessage="BeforeGatewayLink_ClientOutputThread: ho creato la beforeGatewayLink_ClientOutputSocket";
					setChanged();
					notifyObservers(this);
					System.out.println(consoleMessage);
				} catch (IOException e) {
					e.printStackTrace();
				}

				try {
					// creazione stream di input e out da socket
					inSock = new DataInputStream(localBeforeGatewayLink_ClientSocket.getInputStream());
					outSock = new DataOutputStream(localBeforeGatewayLink_ClientSocket.getOutputStream());
				}
				catch (IOException ioe) {
					System.out
					.println("BeforeGatewayLink_ClientOutputThread: Problemi nella creazione degli stream di input/output "
							+ "su socket: ");
					ioe.printStackTrace();
					return;
				}

				/**
				 * Invio il file before_thief.txt al gatewayServer_ServerInputThread del nodo vicino prima di me
				 */

				FtpClientThread f = new FtpClientThread(beforeNeighbour_serverIP, 2121, "./gatewayTmp/before_thief.txt", "after_wants_thief.txt");
				f.setName("BeforeGatewayLink_ClientOutput_ftpClientThread_beforeThiefSend");
				f.start();

				//Attendo che il trasferimento sia completato
				try {
					f.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				consoleMessage="BeforeGatewayLink_ClientOutputThread: inviato before_thief.txt al gatewayServer_ServerInputThread_BEFORE";
				setChanged();
				notifyObservers(this);
				System.out.println(consoleMessage);

				String signal = null;

				while(true) {
					try {
						outSock.writeUTF("BEFORETHIEFREADY");
						lastSignalSent="BEFORETHIEFREADY";
						setChanged();
						notifyObservers(this);
						while(inSock.available()>=0) {
							signal=inSock.readUTF();
							if(signal.equalsIgnoreCase("BEFORETHIEFREADYRECEIVED")) {
								lastSignalReceived=signal;
								consoleMessage="BeforeGatewayLink_ClientOutputThread: Ricevuto "+lastSignalReceived+" ..continuo!";
								setChanged();
								notifyObservers(this);
								System.out.println(consoleMessage);
								/**
								 * se ricevo questa stringa vuol dire che il vicino BEFORE ha ricevuto BEFORETHIEFREADY
								 */
								break;
							}
						}
						break;
					} catch (IOException e) {
						e.printStackTrace();
						System.exit(1);
					}
				}

				try {
					outSock.flush();
				} catch (IOException ioe) {
					System.err.println(Thread.currentThread().getName()+": Eccezione nella flush");
					ioe.printStackTrace();
				}

				/**
				 * ATTENZIONE: devo aspettare qui di ricevere il file got_after_transfer_job.xml
				 */


				consoleMessage="BeforeGatewayLink_ClientOutputThread: Attendo di ricevere AFTERTRANSFERJOBREADY dal GatewayServer_ServerInput_before...";
				setChanged();
				notifyObservers(this);
				System.out.println(consoleMessage);

				try {
					while(inSock.available()>=0) {
						signal=inSock.readUTF();
						if(signal.equalsIgnoreCase("AFTERTRANSFERJOBREADY")) {
							lastSignalReceived=signal;
							consoleMessage=Thread.currentThread().getName()+": Ricevuto "+lastSignalReceived+" ..avvio la procedura!";
							setChanged();
							notifyObservers(this);
							System.out.println(consoleMessage);
							/**
							 * se ricevo questa stringa vuol dire che il vicino before ha inviato al mio ftp server
							 * il file contenente i mac address relativi ai job che gli ho usurpato
							 * CONTROLLO DI SICUREZZA: invio al vicino la stringa AFTERTRANSFERJOBREADYRECEIVED
							 */
							outSock.writeUTF("AFTERTRANSFERJOBREADYRECEIVED");
							lastSignalSent="AFTERTRANSFERJOBREADYRECEIVED";
							setChanged();
							notifyObservers(this);
							break;
						}
					}
				} catch (IOException e) {
					e.printStackTrace();
					System.exit(1);
				}

				try {
					outSock.flush();
				} catch (IOException ioe) {
					System.err.println(Thread.currentThread().getName()+": Eccezione nella flush");
					ioe.printStackTrace();
				}


				consoleMessage="BeforeGatewayLink_ClientOutputThread: ricevuto got_after_transfer_job.xml dal vicino BEFORE.";
				setChanged();
				notifyObservers(this);
				System.out.println(consoleMessage);

				/**
				 * Sblocco il masterJobServerThread, notificandogli così che il file 
				 * got_after_transfer_job.xml è disponibile.
				 * Poi ritorno in attesa all'inizio dell'esecuzione del thread.
				 */

				consoleMessage="BeforeGatewayLink_ClientOutputThread: imposto a false il beforeFlag attualmente con valore "+masterToNotify.getBeforeFlag();
				System.out.println(consoleMessage);
				setChanged();
				notifyObservers(this);
				masterToNotify.setBeforeFlag(false);
				synchronized (semaphore) {
					semaphore.notify();
				}


				consoleMessage="BeforeGatewayLink_ClientOutputThread: ora beforeFlag è "+masterToNotify.getBeforeFlag();
				System.out.println(consoleMessage);
				setChanged();
				notifyObservers(this);

				try {
					consoleMessage="BeforeGatewayLink_ClientOutputThread: Termino e chiudo la socket threadOutputClientSocket";
					inSock.close();
					outSock.close();
					localBeforeGatewayLink_ClientSocket.close();
					System.out.println(consoleMessage);
					setChanged();
					notifyObservers(this);
				} catch (IOException se) {
					se.printStackTrace();
				}


			}//fine while(true);

		}//fine run()

		public void setMasterJobServerThread(MasterJobServer masterJobServer) {
			masterToNotify=masterJobServer;
		}

	}//fine BeforeGatewayLink_ClientOutputThread



	/**
	 * @param beforeGatewayLinkServerInputSocket Server socket sulla quale il gateway è in ascolto per richieste di usurpo da parte del vicino BEFORE
	 * @param beforeNeighbour_ServerInputPort Porta del ServerInput avviato sul before gateway vicino
	 * @param beforeNeighbour_ServerIP Indirizzo IP del ServerInput avviato sul before gateway vicino
	 * @param slaveIP Indirizzo IP locale dello slave
	 * @param slaveDeleteServerPort_before Porta sulla quale trovare il servizio di Cancellazione client sullo slave
	 * @param masterMonitor monitor di sistema Observer sul quale scatenare eventi notifyObserver
	 */
	public BeforeGatewayLink(ServerSocket beforeGatewayLinkServerInputSocket,
			int beforeNeighbour_ServerInputPort, InetAddress beforeNeighbour_ServerIP, InetAddress slaveIP, int slaveDeleteServerPort_before, MasterMonitor masterMonitor) {
		this.beforeGatewayLink_ServerInputSocket=beforeGatewayLinkServerInputSocket;
		this.beforeNeighbour_serverInputPort=beforeNeighbour_ServerInputPort;
		this.beforeNeighbour_serverIP=beforeNeighbour_ServerIP;
		this.slaveIP=slaveIP;
		this.slaveDeleteServerPort_before=slaveDeleteServerPort_before;
		this.masterMonitor=masterMonitor;
		this.addObserver(this.masterMonitor);
	}

	//metodo per lanciare il thread
	public void run() {

		consoleMessage="BeforeGatewayLinkThread avviato...creo i Thread di input e output";

		beforeGatewayLink_ServerInput = new BeforeGatewayLink_ServerInput(beforeGatewayLink_ServerInputSocket,masterMonitor);
		beforeGatewayLink_ServerInputThread = new Thread(beforeGatewayLink_ServerInput);
		beforeGatewayLink_ServerInputThread.setName("beforeGatewayLink_ServerInputThread");

		beforeGatewayLink_ClientOutput = new BeforeGatewayLink_ClientOutput(masterMonitor);
		beforeGatewayLink_ClientOutputThread = new Thread(beforeGatewayLink_ClientOutput);
		beforeGatewayLink_ClientOutputThread.setName("beforeGatewayLink_ClientOutputThread");

		beforeGatewayLink_ServerInputThread.start();
		beforeGatewayLink_ClientOutputThread.start();

		setChanged();
		notifyObservers(this);

		try {
			beforeGatewayLink_ServerInputThread.join();
		} catch (InterruptedException e) {
			System.out.println("\nBeforeGatewayLinkThread: eccezione nel join col thread Input...");
			e.printStackTrace();
		}
		try {
			beforeGatewayLink_ClientOutputThread.join();
		} catch (InterruptedException e) {
			System.out.println("\nBeforeGatewayLinkThread: eccezione nel join col thread Output...");
			e.printStackTrace();
		}

		System.err.println("\nBeforeGatewayLinkThread: termino!");

	}//fine run()

}//fine BeforeGatewayLinkThread
